﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CamShared;
using UPnP;
using System.Net;
using System.Drawing;
using System.Threading;
using System.Diagnostics;
using DavuxLib;

namespace UpnpCameras
{
    public class Output : ICameraOutput
    {
        List<ICamera> Cameras = new List<ICamera>();
        Dictionary<ICamera, Image> Frames = new Dictionary<ICamera, Image>();
        Dictionary<ICamera, object> Locks = new Dictionary<ICamera, object>();
        Dictionary<ICamera, int> Timeout = new Dictionary<ICamera, int>();
        Dictionary<ICamera, bool> Acticated = new Dictionary<ICamera, bool>();

        UPnPDeviceHost upnp = new UPnPDeviceHost();
        HTTPServer http = new HTTPServer(Settings.Get("UPnP Unity|Port", 9595));

        public Output()
        {
            http.OnRequest += new HTTPClient.RequestHandler(http_OnRequest);
            http.Start();

            upnp.Activate();
            Thread t = new Thread(ActiveCheckThread);
            t.Start();
        }

        private void ActiveCheckThread()
        {
            Thread.CurrentThread.Name = "UPNP/OUTPUT Check";
            while (true)
            {
                try
                {
                    for (int i = Timeout.Count -1 ; i >= 0; i--)
                    {
                        KeyValuePair<ICamera, int> k = Timeout.ElementAt(i);

                        Timeout[k.Key]--;

                        if (Timeout[k.Key] <= 0)
                        {
                            if (Acticated[k.Key])
                            {
                                Trace.WriteLine("Deactivating Camera " + k.Key.Name);
                                Acticated[k.Key] = false;
                                k.Key.Deactivate();
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Trace.WriteLine("Error deactivating camera: " + ex);
                }
                Thread.Sleep(1000);
            }
        }


        private static Dictionary<string, string> MakeQueryString(string qs)
        {
            Dictionary<string, string> q = new Dictionary<string, string>();

            if (qs.Contains('&'))
            {
                // multiple values
                string[] pairs = qs.Split(new char[] { '&' });
                foreach (string s in pairs)
                {
                    KeyValuePair<string, string> kv = GetQueryStringValue(s);
                    q.Add(kv.Key, kv.Value);
                }
            }
            else
            {
                KeyValuePair<string, string> kv = GetQueryStringValue(qs);
                q.Add(kv.Key, kv.Value);
            }
            return q;
        }

        private static KeyValuePair<string, string> GetQueryStringValue(string q)
        {
            string key = "";
            string value = "";
            if (q.IndexOf('=') > -1)
            {
                key = q.Substring(0, q.IndexOf('=')).ToLower();
                value = q.Substring(key.Length);
                if (value.Length > 0)
                {
                    value = value.Substring(1);
                }
            }
            else
            {
                key = q;
                value = "";
            }
            return new KeyValuePair<string, string>(key, value);
        }

        void http_OnRequest(HTTPClient client, string url, string method, string body)
        {
            string qs = "";
            Dictionary<string, string> QueryString = new Dictionary<string, string>();

            if (url.Contains('?'))
            {
                qs = url.Substring(url.IndexOf('?'));
                if (qs.Length > 1)
                {
                    qs = qs.Substring(1); // remove ?
                }
                url = url.Substring(0, url.IndexOf('?'));
                QueryString = MakeQueryString(qs);
            }

            if (url.StartsWith("/cam"))
            {
                // cam service
                int camID;
                if (int.TryParse(url.Substring(4), out camID))
                {
                    if (camID >= 0 && camID < Cameras.Count)
                    {
                        ICamera c = Cameras[camID];
                        Timeout[c] = 20;
                        try
                        {
                            if (!c.Active || !Acticated[c])
                            {
                                Acticated[c] = true;
                                c.Activate();
                                Trace.WriteLine("Webserver Activated Camera " + c.Name + " for " + client.Socket.RemoteEndPoint);
                            }
                        }
                        catch (Exception ex)
                        {
                            Trace.WriteLine("Couldn't Activate camera: " + ex.Message);
                        }

                        Image b = Frames[c];
                        if (b != null)
                        {
                            lock (Locks[c])
                            {
                                b = (Image)Frames[c].Clone();
                            }
                            Image result = b;
                            byte[] img = null;

                            if (QueryString.ContainsKey("x") && QueryString.ContainsKey("y"))
                            {
                                try
                                {
                                    result = new Bitmap(int.Parse(QueryString["x"]), int.Parse(QueryString["y"]));
                                    using (Graphics g = Graphics.FromImage(result))
                                    {
                                        g.DrawImage(b, 0, 0, int.Parse(QueryString["x"]), int.Parse(QueryString["y"]));
                                    }
                                }
                                catch (Exception ex)
                                {
                                    Trace.WriteLine("Errorous Request: " + ex);
                                }
                            }

                            long quality = 50;

                            if (QueryString.ContainsKey("q"))
                            {
                                quality = int.Parse(QueryString["q"]);
                            }


                            img = DavuxLib.ImgTools.GetJpeg(result, quality);

                            client.SendResponseHeader("200 OK");
                            client.SendHeader("Content-Type: image/jpeg");
                            client.SendHeader("Content-Length: " + img.Length);
                            client.EndHeader();
                            client.Socket.Send(img);
                        }
                        else
                        {
                            client.SendResponseHeader("200 OK");
                            client.SendHeader("Content-Type: image/jpeg");
                            client.SendHeader("Content-Length: 0");
                            client.EndHeader();
                        }

                        return;
                    }
                }
            }

            /*
            if (url.StartsWith("/cam"))
            {
                // cam service
                int camID;
                if (int.TryParse(url.Substring(4), out camID))
                {
                    if (camID >= 0 && camID < Cameras.Count)
                    {
                        ICamera c = Cameras[camID];
                        Timeout[c] = 5;
                        try
                        {
                            if (!c.Active || !Acticated[c])
                            {
                                c.Activate();
                                Acticated[c] = true;

                                Trace.WriteLine("UPnP Activated Camera " + c.Name + " for " + client.Socket.RemoteEndPoint);
                                //Thread.Sleep(1000); // wait for frame.
                            }
                        }
                        catch (Exception ex)
                        {
                            Trace.WriteLine("Couldn't Activate camera: " + ex.Message);
                        }

                        Image b = Frames[c];
                        if (b != null)
                        {
                            lock (Locks[c])
                            {
                                b = (Image)b.Clone();
                            }
                            Image result = b;
                            byte[] img = null;

                            
                            if (QueryString.ContainsKey("x") && QueryString.ContainsKey("y"))
                            {
                                try
                                {
                                    result = new Bitmap(int.Parse(QueryString["x"]), int.Parse(QueryString["y"]));
                                    using (Graphics g = Graphics.FromImage(result))
                                    {
                                        g.DrawImage(b, 0, 0, int.Parse(QueryString["x"]), int.Parse(QueryString["y"]));
                                    }
                                }
                                catch (Exception ex)
                                {
                                    Trace.WriteLine("Errorous Request: " + ex);
                                }
                            }

                            long quality = 50;

                            if (QueryString.ContainsKey("q"))
                            {
                                quality = int.Parse(QueryString["q"]);
                            }
                            

                            img = DavuxLib.ImgTools.GetJpeg(result, 100);

                            

                            client.SendResponseHeader("200 OK");
                            client.SendHeader("Content-Type: image/jpeg");
                            client.SendHeader("Content-Length: " + img.Length);
                            client.EndHeader();
                            client.Socket.Send(img);
                        }
                        else
                        {
                            client.SendResponseHeader("200 OK");
                            client.SendHeader("Content-Type: image/jpeg");
                            client.SendHeader("Content-Length: 0");
                            client.EndHeader();
                        }

                        return;
                    }
                }
            }
            */
        }

        public void FrameReceived(ICamera camera, System.Drawing.Image frame)
        {
            if (Frames.ContainsKey(camera))
            {
                lock (Locks[camera])
                {
                    Frames[camera] = frame;
                }
            }
        }

        public void ErrorOccured(ICamera camera, string Error, bool Fatal)
        {
            
        }

        int camCount = 0;

        public void CameraAttached(ICamera camera)
        {
            try
            {
                if (camera as UpnpCamera != null)
                {
                    // Trace.WriteLine("Not adding remote Unity camera: " + camera.Name);
                    return;
                }

                if (Settings.Get(camera.Name + "|Unity Sharing", true))
                {




                    Device d = new Device();
                    d.USN = "CamServer 1.1";
                    d.DeviceName = Settings.Get("UPnP Unity|HostName", System.Environment.MachineName) + ": " + camera.Name;

                    d.URL = "http://" + http.LocalEndPoint + ":" + http.Port + "/cam" + camCount;
                    d.Tag = camCount;




                    Frames.Add(camera, null);
                    Locks.Add(camera, new object());
                    Timeout.Add(camera, 0);
                    Acticated.Add(camera, false);
                    Cameras.Add(camera);
                    upnp.Devices.Add(d);
                    camCount++;
                    Trace.WriteLine("Enabled Unity Sharing: " + camera.Name);
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Error in UPNP/OUT CameraAttach: " + ex);
            }
        }

        public string Name
        {
            get { return "UPnP Output"; }
        }

        public void Dispose()
        {
            
        }
    }
}
